Colmi R10 support: pairing fix, on-demand vitals, self-update & PHI-safe diagnostics#6
Open
foureight84 wants to merge 6 commits into
Open
Colmi R10 support: pairing fix, on-demand vitals, self-update & PHI-safe diagnostics#6foureight84 wants to merge 6 commits into
foureight84 wants to merge 6 commits into
Conversation
The R10 connected and discovered services but never finished pairing,
sitting on "Connecting…" forever. Root cause: Android's BLE stack allows
only one outstanding GATT operation at a time, and onServicesDiscovered
issued a firmware readCharacteristic (on the 0x180A DIS service the R10
exposes but the R02 does not) before the CCCD descriptor write that gates
the CONNECTED transition — so the descriptor write was silently dropped.
- Replace the write-only queue with a unified FIFO GATT operation queue
(CommandWrite / Read / DescriptorWrite). Every read, write, and CCCD
descriptor write now runs strictly one-at-a-time, each retired by its
completion callback. Notify-CCCD writes are enqueued first so the ring
reaches CONNECTED before informational battery/firmware reads.
- Check the boolean each GATT call returns; log and skip on rejection
instead of stranding the queue. The completion-timeout guard now covers
every op type, not just writes.
- Fix the cosmetic pairing badge: derive the real model from the advertised
name ("COLMI R10_1203" → "Colmi R10") instead of the family displayName,
which mislabeled every Colmi as "Colmi R02".
Verified on a Pixel 8: the R10 now reaches CONNECTED and syncs live data.
Colmi rings never showed the Vitals "Measure" button: it was gated on BLOOD_PRESSURE/BLOOD_SUGAR (the Jring combined 0x23 packet), which Colmi hardware doesn't have. Colmi did already support on-demand HR (0x69) but only exposed it via the coach tool, and had no live SpO2 path at all. Add live SpO2 via the real-time command family, then surface a spot measurement (HR + SpO2) both as a Vitals button and automatically on connect. - ColmiEncoder/Protocol: live SpO2 start ([0x69, reading_type=3, START]) and stop ([0x6A, 3, 0, 0]), per the colmi_r02_client real-time protocol. - ColmiDecoder: branch the 0x69 real-time response on reading_type so type 3 decodes to Spo2Result (value at v[3]); the HR path is unchanged. - ColmiSyncEngine: startSpO2/stopSpO2 now drive the live spot command (historical big-data SpO2 sync is untouched). Add MANUAL_SPO2 capability. - RingSyncCoordinator: measureSpot() runs HR then SpO2, capability-gated; autoMeasureOnConnect() fires it ~2s after connect. Wired into onConnected. - Vitals screen: show the Measure button for rings with manual HR/SpO2 (spot mode) alongside the existing combined flow, with its own countdown and "measuring heart rate & SpO2" copy. Verified on a Pixel 8 with an R10: auto-measure on connect populated HR and SpO2 (96%), the value confirmed against the ring's raw 0x69/3 frame, and the manual Measure button runs the spot flow.
Builds on the R10 pairing fix + measurement feature with the support, observability, and correctness work needed to ship it. Self-update (release-only): - In-app updater polls the GitHub latest release, compares versionCode from the `v<name>+<code>` tag, downloads + installs the universal APK. On-launch (throttled) + a Settings "Check for updates" button. - Tag-driven GitHub Actions workflow builds, signs, and attaches the APKs. - Coexisting `.debug` build (applicationIdSuffix) so a debug install never wipes the release app's data; enable buildConfig; env-overridable signing. Diagnostics + privacy: - Harden the export: own-PID logcat capture + a crash handler (new Application) persisting stack traces, plus accurate build/version info. - PHI scrubbing on by default (health values, ring serial, MAC addresses removed; models/opcodes/control frames/errors kept) with an opt-out toggle for full unmasked BLE frames. Verified masked vs full on-device. Ring removal (restores the iOS two-action model): - "Forget" is now non-destructive (unbind + disconnect only) for both rings — no more power-off/factory-reset wiping a Colmi's history on a normal remove. - New Colmi-only "Factory Reset" syncs latest history first, then resets, behind a confirmation dialog. Fixes: - Persist + prettify the connected ring's name (was always showing the default "SMART_RING"; now "Colmi R10"); shared ringModelLabel. - Live-activity (0x73 0x12) decode read big-endian fields as little-endian, inflating steps to millions and locking a garbage daily total via max-merge. Decode big-endian + plausibility guard + self-heal of a stuck total. - Drop autoMeasureOnConnect: connecting no longer pins the optical sensor on. Matches iOS — connect runs the history sync only; vitals come from the ring's periodic monitoring + the manual Measure button.
End users get diagnostics from the in-app export, so there's no need to distribute a debug APK. The debug variant stays for local dev/repro (adb, run-as, system-Bluetooth logs); it's just no longer a release asset.
Two reasons the Colmi green LED kept pulsing after a measurement: - The HR stop frame was wrong: manualHeartRate(false) sent [0x69, 0x02], but 0x69 is CMD_START_REAL_TIME — so the 'stop' actually started another real-time reading and the sensor never switched off (the ring only timed out on its own). Send CMD_STOP_REAL_TIME (0x6A) instead, mirroring SpO2. - measureHR/measureSpO2/measureCombined sent the stop after the wait loop, not in a finally. The Measure button runs in the screen's coroutine scope, so navigating away mid-measurement cancelled it before the stop ran. Wrap each in try/finally so the sensor is always switched off.
Contributor
|
I did test this one too. I have a JRing, not a Colmi, so I wasnt able to test that side of it but everything else worked great. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds out Colmi R10 support and the surrounding shippability work, verified on a Pixel 8 with a real R10.
Connectivity
onServicesDiscoveredissued a firmware read (on the0x180ADIS service the R10 exposes but the R02 doesn't) before the CCCD write that gatesCONNECTED, so the descriptor write was silently dropped. Replaced the write-only queue with a unified, serialized GATT op queue (notify-enable first). Also fixed the cosmetic pairing badge ("COLMI R10_1203" → "Colmi R10").Vitals
0x69), reverse-engineered and verified against the ring. Surfaced as a Vitals "Measure" button (Colmi-only spot flow alongside the Jring combined flow).0x73 0x12) frame packs steps/cal/distance big-endian; we read them little-endian, inflating steps to millions and locking a garbage daily total via the max-merge. Now decoded big-endian + plausibility guard + self-heal of a stuck total.autoMeasureOnConnect— it pinned the optical sensor on every (re)connect. Now matches iOS: connect runs the history sync only; vitals come from the ring's periodic monitoring + the manual Measure button.Self-update (release-only)
versionCodefrom thev<name>+<code>tag, downloads + installs the universal APK. On-launch (throttled) + a Settings "Check for updates" button..debugbuild (applicationIdSuffix) so a debug install never wipes the release app's data.Diagnostics + privacy
BluetoothGattcallbacks) + a crash handler, plus accurate build info.Ring removal (restores the iOS two-action model)
Misc
Test notes
Verified on-device: R10 reaches CONNECTED and syncs; auto/manual measure produced live HR + SpO₂ (96–98%); masked vs full diagnostics export confirmed (vital values + serial + MACs removed when masked); steps/cal/distance corrected (9.5M → 249);
.debugcoexists with the release app without data loss.